import psutil
from flask import Flask, render_template, request, redirect, url_for, jsonify
import json
import subprocess
import os
from serial.tools import list_ports
import shutil

# 导入钉钉推送模块
from dingtalk_bot import DingTalkBot
from heartbeat_client import logger

app = Flask(__name__)

# 获取项目路径
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_DIR = os.path.dirname(BASE_DIR)
CONFIG_DIR = os.path.join(PROJECT_DIR, "config")
CONFIG_FILE = os.path.join(CONFIG_DIR, "main_config.json")
DINGTALK_FILE = os.path.join(CONFIG_DIR, "dingtalk_config.json")
LAYOUT_FILE = os.path.join(CONFIG_DIR, "layout_config.json")
RUN_CONFIG_FILE = os.path.join(CONFIG_DIR, "run_config.json")
REBOOT_CONFIG_FILE = os.path.join(CONFIG_DIR, "reboot_config.json")
VENV_PYTHON = os.path.join(PROJECT_DIR, ".venv", "Scripts", "python.exe")
TEST_SCRIPT = os.path.join(BASE_DIR, "reboot_monitor.py")
BAT_FILE = os.path.join(PROJECT_DIR, "logtracker_startup.bat")
STARTUP_FOLDER = os.path.join(os.getenv("APPDATA"), "Microsoft", "Windows", "Start Menu", "Programs", "Startup")


# 全局变量
script_process = None


# 工具函数：读取钉钉配置
def load_dingtalk_config():
	try:
		with open(DINGTALK_FILE, "r", encoding="utf-8") as f:
			return json.load(f)
	except FileNotFoundError:
		return []


# 工具函数：生成开机启动脚本
def update_startup_bat():
	try:
		bat_content = f'''@echo off
cd /d "{PROJECT_DIR}"
.venv\\Scripts\\python.exe src\\reboot_monitor.py
'''
		with open(BAT_FILE, "w", encoding="utf-8") as f:
			f.write(bat_content)
		startup_bat_path = os.path.join(STARTUP_FOLDER, "logtracker_startup.bat")
		shutil.copy(BAT_FILE, startup_bat_path)
		return True, "成功更新开机启动脚本"
	except Exception as e:
		return False, f"更新开机启动脚本失败：{str(e)}"


# 路由：主页面
@app.route("/")
def index():
	try:
		with open(CONFIG_FILE, "r", encoding="utf-8") as f:
			config = json.load(f)
	except FileNotFoundError:
		config = []
	
	try:
		with open(LAYOUT_FILE, "r", encoding="utf-8") as f:
			layout_config = json.load(f)
		layout_keys = list(layout_config.keys())
	except FileNotFoundError:
		layout_keys = []
	
	try:
		dingtalk_config = load_dingtalk_config()
		dingtalk_groups = [group['group_name'] for group in dingtalk_config if 'group_name' in group]
	except (FileNotFoundError, KeyError, TypeError):
		dingtalk_groups = []
	
	try:
		ports = [port.device for port in list_ports.comports()]
	except Exception as e:
		ports = ["COM1", "COM2"]
		print("无法获取串口列表:", e)
	
	try:
		with open(RUN_CONFIG_FILE, "r", encoding="utf-8") as f:
			run_config = json.load(f)
	except FileNotFoundError:
		run_config = {"interval": "", "upload_url": ""}
	
	try:
		with open(REBOOT_CONFIG_FILE, "r", encoding="utf-8") as f:
			reboot_config = json.load(f)
	except FileNotFoundError:
		reboot_config = {"pc_name": "", "push_group": "", "heartbeat_interval": 5, "enabled": False}
	
	return render_template(
		"index.html",
		saved_config=config,
		layout_options=layout_keys,
		push_options=dingtalk_groups,
		serial_ports=ports,
		run_config=run_config,
		reboot_config=reboot_config
	)


# 路由：保存主配置
@app.route("/save", methods=["POST"])
def save():
	ports = request.form.getlist("port")
	boards = request.form.getlist("board")
	log_files = request.form.getlist("log_file")
	pushes = request.form.getlist("push")
	enableds = request.form.getlist("enabled")
	
	config = []
	for i in range(len(ports)):
		item = {
			"port": ports[i],
			"board": boards[i],
			"log_file": log_files[i],
			"push": pushes[i] if i < len(pushes) else "",
			"enabled": i < len(enableds)
		}
		config.append(item)
	
	with open(CONFIG_FILE, "w", encoding="utf-8") as f:
		json.dump(config, f, indent=2, ensure_ascii=False)
	
	return redirect(url_for("index"))


# 路由：钉钉配置页面
@app.route("/dingtalk")
def dingtalk():
	config = load_dingtalk_config()
	return render_template("dingtalk.html", dingtalk_config=config)


# 路由：保存钉钉配置
@app.route("/save_dingtalk", methods=["POST"])
def save_dingtalk():
	group_names = request.form.getlist("group_name")
	webhooks = request.form.getlist("webhook")
	secrets = request.form.getlist("secret")
	mobiles = request.form.getlist("at_mobiles")
	
	config = []
	for name, webhook, secret, mobile in zip(group_names, webhooks, secrets, mobiles):
		if not name.strip() or not webhook.strip():
			continue
		config.append({
			"group_name": name.strip(),
			"webhook": webhook.strip(),
			"secret": secret.strip(),
			"at_mobiles": [m.strip() for m in mobile.split(",") if m.strip()]
		})
	
	with open(DINGTALK_FILE, "w", encoding="utf-8") as f:
		json.dump(config, f, indent=2, ensure_ascii=False)
	
	return redirect(url_for("dingtalk"))


# 路由：发送钉钉测试消息
@app.route("/send-test-dingtalk", methods=["POST"])
def send_test_dingtalk():
	data = request.get_json()
	webhook = data.get("webhook")
	secret = data.get("secret")
	at_mobiles = data.get("at_mobiles", [])
	group_name = data.get("group_name", "测试群")
	
	if not webhook or not secret:
		return jsonify({"success": False, "message": "缺少 webhook 或 secret"})
	
	config = {
		"group_name": group_name,
		"webhook": webhook,
		"secret": secret,
		"at_mobiles": at_mobiles
	}
	
	try:
		bot = DingTalkBot(config)
		result = bot.send_text("这是一条来自配置页面的测试消息，请注意查收。")
		return jsonify({"success": True, "result": result})
	except Exception as e:
		return jsonify({"success": False, "message": str(e)})


# 路由：版型配置页面
@app.route("/layout", methods=["GET", "POST"])
def layout():
	try:
		with open(LAYOUT_FILE, "r", encoding="utf-8") as f:
			layouts = json.load(f)
	except FileNotFoundError:
		layouts = {}
	
	if request.method == "POST":
		action = request.form.get("action")
		new_layout_name = request.form.get("new_layout_name", "").strip()
		if action == "add_layout" and new_layout_name and new_layout_name not in layouts:
			layouts[new_layout_name] = {}
		
		layout_to_delete = request.form.get("delete_layout")
		if layout_to_delete in layouts:
			layouts.pop(layout_to_delete)
		
		for layout_name in list(layouts.keys()):
			key_to_delete = request.form.get(f"delete_keyword_{layout_name}")
			if key_to_delete and key_to_delete in layouts[layout_name]:
				layouts[layout_name].pop(key_to_delete)
		
		layout_to_save = request.form.get("save_keywords")
		if layout_to_save in layouts:
			new_key = request.form.get(f"new_keyword_name_{layout_to_save}", "").strip()
			new_value = request.form.get(f"new_keyword_value_{layout_to_save}", "").strip()
			if new_key and new_value:
				layouts[layout_to_save][new_key] = new_value
			
			keyword_names = request.form.getlist(f"keyword_name_{layout_to_save}")
			keyword_values = request.form.getlist(f"keyword_value_{layout_to_save}")
			updated = {}
			for k, v in zip(keyword_names, keyword_values):
				if k.strip() and v.strip():
					updated[k.strip()] = v.strip()
			layouts[layout_to_save].update(updated)
		
		with open(LAYOUT_FILE, "w", encoding="utf-8") as f:
			json.dump(layouts, f, indent=2, ensure_ascii=False)
		
		return redirect(url_for("layout"))
	
	return render_template("layout.html", layouts=layouts)


# 路由：保存运行配置
@app.route("/save_run_config", methods=["POST"])
def save_run_config():
	interval = request.form.get("interval")
	upload_url = request.form.get("upload_url")
	
	run_config = {
		"interval": interval,
		"upload_url": upload_url
	}
	
	with open(RUN_CONFIG_FILE, "w", encoding="utf-8") as f:
		json.dump(run_config, f, indent=2, ensure_ascii=False)
	
	return redirect(url_for("index"))


@app.route("/save_reboot_config", methods=["POST"])
def save_reboot_config():
	pc_name = request.form.get("reboot_pc_name", "").strip()
	push_group = request.form.get("reboot_push_group", "")
	heartbeat_interval = request.form.get("heartbeat_interval", "600")  # 默认心跳间隔设为 600 秒
	enabled = "reboot_enabled" in request.form
	
	config = {
		"pc_name": pc_name,
		"push_group": push_group,
		"heartbeat_interval": int(heartbeat_interval),
		"enabled": enabled
	}
	
	with open(REBOOT_CONFIG_FILE, "w", encoding="utf-8") as f:
		json.dump(config, f, indent=2, ensure_ascii=False)
	
	# success, message = update_startup_bat()
	# if not success:
	# 	return f"<script>alert('{message}'); window.history.back();</script>"
	
	# 终止之前的 heartbeat_client.py 进程
	def terminate_process(name):
		for proc in psutil.process_iter(['pid', 'name']):
			try:
				if name.lower() in proc.info['name'].lower():
					proc.kill()
					logger.info(f"终止进程: {proc.info['name']} (PID: {proc.info['pid']})")
			except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
				pass
	
	terminate_process("heartbeat_client.py")
	
	# 生成 run_heartbeat_client.bat 文件
	bat_content = f'''@echo off
cd /d "{PROJECT_DIR}\\src"
"{VENV_PYTHON}" "heartbeat_client.py" --pc_name "{pc_name}" --interval "{heartbeat_interval}"
'''
	bat_file_path = os.path.join(PROJECT_DIR, "run_heartbeat_client.bat")
	
	try:
		with open(bat_file_path, "w", encoding="utf-8") as f:
			f.write(bat_content)
		logger.info(f"成功生成 {bat_file_path}")
	except Exception as e:
		logger.error(f"生成 {bat_file_path} 失败: {str(e)}")
		return f"<script>alert('生成批处理文件失败: {str(e)}'); window.history.back();</script>"
	
	# 将批处理文件复制到 Windows 启动文件夹
	startup_folder = os.path.join(os.getenv("APPDATA"), "Microsoft", "Windows", "Start Menu", "Programs", "Startup")
	startup_bat_path = os.path.join(startup_folder, "run_heartbeat_client.bat")
	
	# 检查并删除已存在的批处理文件
	if os.path.exists(startup_bat_path):
		try:
			os.remove(startup_bat_path)
			logger.info(f"成功删除已存在的 {startup_bat_path}")
		except Exception as e:
			logger.error(f"删除已存在的 {startup_bat_path} 失败: {str(e)}")
			return f"<script>alert('删除已存在的批处理文件失败: {str(e)}'); window.history.back();</script>"
	
	try:
		shutil.copy(bat_file_path, startup_bat_path)
		logger.info(f"成功将 {bat_file_path} 复制到 {startup_bat_path}")
	except Exception as e:
		logger.error(f"复制 {bat_file_path} 到 {startup_bat_path} 失败: {str(e)}")
		return f"<script>alert('复制批处理文件失败: {str(e)}'); window.history.back();</script>"
	
	# 只有在启用心跳功能时才执行脚本
	if enabled:
		heartbeat_client_path = os.path.join(PROJECT_DIR, "src", "heartbeat_client.py")
		command = [VENV_PYTHON, heartbeat_client_path, "--pc_name", pc_name, "--interval", heartbeat_interval]
		
		logger.info(f"Executing command: {' '.join(command)}")
		
		try:
			result = subprocess.run(
				command,
				check=True,
				stdout=subprocess.PIPE,
				stderr=subprocess.PIPE,
				text=True,
				encoding='utf-8'
			)
			logger.info(f"心跳客户端执行成功：{result.stdout}")
		except subprocess.CalledProcessError as e:
			logger.error(f"心跳客户端执行失败：{e.stderr}")
			return f"<script>alert('心跳客户端执行失败: {e.stderr}'); window.history.back();</script>"
		except Exception as e:
			logger.error(f"心跳客户端执行失败: {str(e)}")
			return f"<script>alert('心跳客户端执行失败: {str(e)}'); window.history.back();</script>"
	
	return redirect(url_for("index"))


# 路由：启动脚本
@app.route("/start-script", methods=["POST"])
def start_script():
	global script_process
	if script_process is None:
		try:
			script_process = subprocess.Popen(
				[VENV_PYTHON, TEST_SCRIPT],
				stdout=subprocess.PIPE,
				stderr=subprocess.STDOUT,
				text=True,
				bufsize=1,
				universal_newlines=True
			)
			return jsonify({"status": "started"})
		except Exception as e:
			return jsonify({"status": "error", "message": str(e)})
	else:
		return jsonify({"status": "already_running"})


# 路由：停止脚本
@app.route("/stop-script", methods=["POST"])
def stop_script():
	global script_process
	if script_process:
		script_process.terminate()
		script_process = None
		return jsonify({"status": "stopped"})
	else:
		return jsonify({"status": "not_running"})


# 路由：获取脚本输出
@app.route("/script-output")
def get_output():
	global script_process
	if script_process:
		output = script_process.stdout.read()
		return jsonify({"running": True, "output": output})
	else:
		return jsonify({"running": False, "output": ""})


if __name__ == "__main__":
	app.run(host='0.0.0.0', port=5000, debug=True)



